/*
* json is a C++ wrapper for jansson.
*
* @author: dzlua
* @contact: 505544956@qq.com
* @date: August 17, 2020
* @example: https://gitee.com/dzlua/json.git
*/

#ifndef __JSON_HPP_
#define __JSON_HPP_

#include <jansson.h>
#include <functional>

namespace json {
  
// Warpper a C style class to c++ class.
// https://gitee.com/dzlua/test/blob/master/WrapperC/wrapper.hpp
template <typename T, typename C>
class Wrapper {
  public:
    Wrapper(C *cptr = nullptr) noexcept { reset(cptr); }
    Wrapper(const Wrapper<T,C>& r) noexcept { reset(r.get()); }
    Wrapper<T,C>& operator=(const Wrapper<T,C>& r) noexcept { reset(r.get()); return *this; }
    Wrapper<T,C>& operator=(C *cptr) noexcept { reset(cptr); return *this; }
  public:
    inline T* operator->() noexcept { return &obj_; }
    inline operator C*() const noexcept { return get(); }
    inline explicit operator bool() const noexcept { return get() != nullptr; }
  public:
    inline C* get() const noexcept { return obj_._get(); }
    inline C*& set() noexcept { return obj_._set(); }
    inline void reset(C *cptr = nullptr) noexcept { obj_._reset(cptr); }
    inline void swap(Wrapper<T,C>& r) noexcept { obj_._swap(r.obj_); }
  protected:
    T obj_;
};

template <typename T, typename C>
inline bool operator==(const Wrapper<T,C>& l, const Wrapper<T,C>& r) noexcept { return l.get() == r.get(); }
template <typename T, typename C>
inline bool operator==(const Wrapper<T,C>& l, std::nullptr_t) noexcept { return l.get() == nullptr; }
template <typename T, typename C>
inline bool operator==(std::nullptr_t, const Wrapper<T,C>& r) noexcept { return nullptr == r.get(); }
template <typename T, typename C>
inline bool operator!=(const Wrapper<T,C>& l, const Wrapper<T,C>& r) noexcept { return l.get() != r.get(); }
template <typename T, typename C>
inline bool operator!=(const Wrapper<T,C>& l, std::nullptr_t) noexcept { return l.get() != nullptr; }
template <typename T, typename C>
inline bool operator!=(std::nullptr_t, const Wrapper<T,C>& r) noexcept { return nullptr != r.get(); }

template <typename T, typename C>
class ClassWrapped {
    friend class Wrapper<T,C>;
  protected:
    ClassWrapped() : cptr_(nullptr) {}
    virtual ~ClassWrapped() { _reset(nullptr); }
    inline C* _get() const noexcept { return cptr_; }
    inline C*& _set() noexcept { return cptr_; }
    inline void _reset(C *cptr = nullptr) noexcept { cptr_ = cptr; }
    inline void _swap(ClassWrapped<T,C>& r) noexcept { std::swap(cptr_, r.cptr_); }
  protected:
    C* cptr_;
};

// macros for wrapper json.
#define JSON_WRAPPER_CLASS_BEGIN(classname,jsonstruct) \
  class classname : public ClassWrapped<classname, jsonstruct> { \
      friend class Wrapper<classname,jsonstruct>; \
    protected: \
      classname() = default; \
    public: \
      typedef Wrapper<classname,jsonstruct> Wrapped;
#define JSON_WRAPPER_CLASS_END };

// macros for json child class.
#define JSON_CLASS_BEGIN(classname) \
  class classname : public Json { \
      friend class Wrapper<classname,json_t>; \
    protected: \
      classname() = default; \
    public: \
      typedef Wrapper<classname,json_t> Wrapped;
#define JSON_CLASS_END };

// base
typedef json_type Type;
typedef json_int_t int_t;
typedef int bool_t;
typedef enum json_error_code ErrorCode;
typedef json_error_t error_t;
typedef json_load_callback_t load_callback_t;
typedef json_dump_callback_t dump_callback_t;
typedef json_malloc_t malloc_t;
typedef json_free_t free_t;

namespace detail {
  JSON_WRAPPER_CLASS_BEGIN(Json, json_t)
    inline Type type() { return json_typeof(cptr_); }
    inline bool isObject() { return json_is_object(cptr_); }
    inline bool isArray() { return json_is_array(cptr_); }
    inline bool isString() { return json_is_string(cptr_); }
    inline bool isInteger() { return json_is_integer(cptr_); }
    inline bool isReal() { return json_is_real(cptr_); }
    inline bool isNumber() { return json_is_number(cptr_); }
    inline bool isTrue() { return json_is_true(cptr_); }
    inline bool isFalse() { return json_is_false(cptr_); }
    inline bool isBoolean() { return json_is_boolean(cptr_); }
    inline bool isNull() { return json_is_null(cptr_); }
    inline virtual bool isOK() { return isNull(); }

    template <typename T>
    inline T trans() { return T(cptr_); }

    inline void incref() { json_incref(cptr_); }
    inline void decref() { json_decref(cptr_); }

    template <typename ...Args>
    inline bool unpack(const char *fmt, Args ... args) {
      return 0 == json_unpack(cptr_, fmt, args...); }

    template <typename ...Args>
    inline bool unpackEx(error_t *error, size_t flags, const char *fmt, Args ... args) {
      return 0 == json_unpack_ex(cptr_, error, flags, fmt, args...); }

    inline bool vnpackEx(error_t *error, size_t flags, const char *fmt, va_list ap) {
      return 0 == json_vunpack_ex(cptr_, error, flags, fmt, ap); }

    inline bool equal(const json_t *other) { return 0 == json_equal(cptr_, other); }

    inline Json::Wrapped copy() {
      return Json::Wrapped(json_copy(cptr_)); }
    template <typename T>
    inline T copy() { return T(json_copy(cptr_)); }

    inline Json::Wrapped deepCopy() {
      return Json::Wrapped(json_deep_copy(cptr_)); }
    template <typename T>
    inline T deepCopy() { return T(json_deep_copy(cptr_)); }

    inline char* dumps(size_t flags = JSON_COMPACT) { return json_dumps(cptr_, flags); }
    inline size_t dumpb(char *buffer, size_t size, size_t flags = JSON_COMPACT) {
      return json_dumpb(cptr_, buffer, size, flags); }
    inline bool dumpf(FILE *output, size_t flags = JSON_COMPACT) {
      return 0 == json_dumpf(cptr_, output, flags); }
    inline bool dumpfd(int output, size_t flags = JSON_COMPACT) {
      return 0 == json_dumpfd(cptr_, output, flags); }
    inline bool dumpfile(const char *path, size_t flags = JSON_COMPACT) {
      return 0 == json_dump_file(cptr_, path, flags); }
    inline bool dumpCallback(dump_callback_t callback, void *data, size_t flags = JSON_COMPACT) {
      return 0 == json_dump_callback(cptr_, callback, data, flags); }
  JSON_WRAPPER_CLASS_END

  JSON_WRAPPER_CLASS_BEGIN(ObjectIter, void)
    inline Wrapped next(json_t *object) {
      return Wrapped(json_object_iter_next(object, cptr_)); }
    inline bool setNew(json_t *object, json_t *value) {
      return 0 == json_object_iter_set_new(object, cptr_, value); }
    inline bool set(json_t *object, json_t *value) {
      return 0 == json_object_iter_set(object, cptr_, value); }
    inline const char* key() { return json_object_iter_key(cptr_); }
    inline Json::Wrapped value() {
      return Json::Wrapped(json_object_iter_value(cptr_)); }
  JSON_WRAPPER_CLASS_END

  JSON_CLASS_BEGIN(Object)
    inline static void seed(size_t seed_) { json_object_seed(seed_); }
    inline size_t size() { return json_object_size(cptr_); }

    inline Json::Wrapped get(const char *key) {
      return Json::Wrapped(json_object_get(cptr_, key)); }
    template <typename T>
    inline T get(const char *key) { return T(json_object_get(cptr_, key)); }

    inline bool setNew(const char *key, json_t *value) {
      return 0 == json_object_set_new(cptr_, key, value); }
    inline bool setNewNoCheck(const char *key, json_t *value) {
      return 0 == json_object_set_new_nocheck(cptr_, key, value); }
    inline bool set(const char *key, json_t *value) {
      return 0 == json_object_set(cptr_, key, value); }
    inline bool setNoCheck(const char *key, json_t *value) {
      return 0 == json_object_set_nocheck(cptr_, key, value); }

    inline bool del(const char *key) { return 0 == json_object_del(cptr_, key); }
    inline bool clear() { return 0 == json_object_clear(cptr_); }

    inline bool update(json_t *other) { return 0 == json_object_update(cptr_, other); }
    inline bool updateExisting(json_t *other) {
      return 0 == json_object_update_existing(cptr_, other); }
    inline bool updateMissing(json_t *other) {
      return 0 == json_object_update_missing(cptr_, other); }
    inline bool updateRecursive(json_t *other) {
      return 0 == json_object_update_recursive(cptr_, other); }
    inline bool updateNew(json_t *other) {
      return 0 == json_object_update_new(cptr_, other); }
    inline bool updateExistingNew(json_t *other) {
      return 0 == json_object_update_existing_new(cptr_, other); }
    inline bool updateMissingNew(json_t *other) {
      return 0 == json_object_update_missing_new(cptr_, other); }

    inline ObjectIter::Wrapped iter() {
      return ObjectIter::Wrapped(json_object_iter(cptr_)); }
    inline ObjectIter::Wrapped iterAt(const char *key) {
      return ObjectIter::Wrapped(json_object_iter_at(cptr_, key)); }
    static inline ObjectIter::Wrapped keyToIter(const char *key) {
      return ObjectIter::Wrapped(json_object_key_to_iter(key)); }

    inline virtual bool isOK() override { return isObject(); }

    inline void foreach(std::function<bool(const char* key, Json::Wrapped &v)> each) {
      const char* key;
      json_t *value;
      Json::Wrapped v(nullptr);
      json_object_foreach(cptr_, key, value) {
        v.reset(value);
        if (!each(key, v))
          break;
      }
    }
    template <typename T>
    inline void foreach(std::function<bool(const char* key, T &v)> each) {
      const char* key;
      json_t *value;
      T v(nullptr);
      json_object_foreach(cptr_, key, value) {
        v.reset(value);
        if (!each(key, v))
          break;
      }
    }
  JSON_CLASS_END
  
  JSON_CLASS_BEGIN(Array)
    inline size_t size() { return json_array_size(cptr_); }

    inline Json::Wrapped get(size_t index) {
      return Json::Wrapped(json_array_get(cptr_, index)); }
    template <typename T>
    inline T get(size_t index) { return T(json_array_get(cptr_, index)); }

    inline bool setNew(size_t index, json_t *value) {
      return 0 == json_array_set_new(cptr_, index, value); }
    inline bool appendNew(json_t *value) {
      return 0 == json_array_append_new(cptr_, value); }
    inline bool insertNew(size_t index, json_t *value) {
      return 0 == json_array_insert_new(cptr_, index, value); }
    inline bool set(size_t index, json_t *value) {
      return 0 == json_array_set(cptr_, index, value); }
    inline bool append(json_t *value) {
      return 0 == json_array_append(cptr_, value); }
    inline bool insert(size_t index, json_t *value) {
      return 0 == json_array_insert(cptr_, index, value); }

    inline bool remove(size_t index) {
      return 0 == json_array_remove(cptr_, index); }
    inline bool clear() { return 0 == json_array_clear(cptr_); }
    inline bool extend(size_t index, json_t *other) {
      return 0 == json_array_extend(cptr_, other); }

    inline virtual bool isOK() override { return isArray(); }

    inline void foreach(std::function<bool(size_t index, Json::Wrapped &v)> each) {
      size_t index;
      json_t *value;
      Json::Wrapped v(nullptr);
      json_array_foreach(cptr_, index, value) {
        v.reset(value);
        if (!each(index, v))
          break;
      }
    }
    template <typename T>
    inline void foreach(std::function<bool(size_t index, T &v)> each) {
      size_t index;
      json_t *value;
      T v(nullptr);
      json_array_foreach(cptr_, index, value) {
        v.reset(value);
        if (!each(index, v))
          break;
      }
    }
  JSON_CLASS_END

  JSON_CLASS_BEGIN(String)
    inline const char* value() { return json_string_value(cptr_); }
    inline size_t length() { return json_string_length(cptr_); }
    inline bool set(const char *value) {
      return 0 == json_string_set(cptr_, value); }
    inline bool set(const char *value, size_t len) {
      return 0 == json_string_setn(cptr_, value, len); }
    inline bool setNoCheck(const char *value) {
      return 0 == json_string_set_nocheck(cptr_, value); }
    inline bool setNoCheck(const char *value, size_t len) {
      return 0 == json_string_setn_nocheck(cptr_, value, len); }
    inline virtual bool isOK() override { return isString(); }
  JSON_CLASS_END

  JSON_CLASS_BEGIN(Integer)
    inline int_t value() { return json_integer_value(cptr_); }
    inline bool set(int_t value) {
      return 0 == json_integer_set(cptr_, value); }
    inline virtual bool isOK() override { return isInteger(); }
  JSON_CLASS_END

  JSON_CLASS_BEGIN(Real)
    inline double value() { return json_real_value(cptr_); }
    inline bool set(double value) {
      return 0 == json_real_set(cptr_, value); }
    inline virtual bool isOK() override { return isReal(); }
  JSON_CLASS_END

  JSON_CLASS_BEGIN(Number)
    inline double value() { return json_number_value(cptr_); }
    inline bool set(double value) {
      return 0 == json_real_set(cptr_, value); }
    inline virtual bool isOK() override { return isNumber(); }
  JSON_CLASS_END

  JSON_CLASS_BEGIN(Boolean)
    inline bool value() { return json_is_true(cptr_); }
    inline virtual bool isOK() override { return isBoolean(); }
  JSON_CLASS_END
} // end namespace detail

typedef detail::Json::Wrapped Json;
typedef detail::Object::Wrapped Object;
typedef detail::Array::Wrapped Array;
typedef detail::String::Wrapped String;
typedef detail::Integer::Wrapped Integer;
typedef detail::Real::Wrapped Real;
typedef detail::Number::Wrapped Number;
typedef detail::Boolean::Wrapped Boolean;

inline Object object() { return Object(json_object()); }
inline Array array() { return Array(json_array()); }
inline String string(const char *value) {
  return String(json_string(value)); }
inline String string(const char *value, size_t len) {
  return String(json_stringn(value, len)); }
inline String stringNoCheck(const char *value) {
  return String(json_string_nocheck(value)); }
inline String stringNoCheck(const char *value, size_t len) {
  return String(json_stringn_nocheck(value, len)); }
inline Integer integer(int_t value) {
  return Integer(json_integer(value)); }
inline Real real(double value) {
  return Real(json_real(value)); }
inline Number number(double value) {
  return Number(json_real(value)); }
inline Boolean boolean(bool value) {
  return Boolean(json_boolean(value)); }
inline Json null() { return Json(json_null()); }

// pack
template <typename ...Args>
inline Json pack(const char *fmt, Args ... args) {
  return Json(json_pack(fmt, args...)); }
template <typename T, typename ...Args>
inline T pack(const char *fmt, Args ... args) {
  return T(json_pack(fmt, args...)); }

template <typename ...Args>
inline Json packEx(error_t *error, size_t flags, const char *fmt, Args ... args) {
  return Json(json_pack_ex(error, flags, fmt, args...)); }
template <typename T, typename ...Args>
inline T packEx(error_t *error, size_t flags, const char *fmt, Args ... args) {
  return T(json_pack_ex(error, flags, fmt, args...)); }

inline Json vpackEx(error_t *error, size_t flags, const char *fmt, va_list ap) {
  return Json(json_vpack_ex(error, flags, fmt, ap)); }
template <typename T>
inline T vpackEx(error_t *error, size_t flags, const char *fmt, va_list ap) {
  return T(json_vpack_ex(error, flags, fmt, ap)); }

// sprintf
template <typename ...Args>
inline Json sprintf(const char *fmt, Args ... args) {
  return Json(json_sprintf(fmt, args...)); }
template <typename T, typename ...Args>
inline T sprintf(const char *fmt, Args ... args) {
  return T(json_sprintf(fmt, args...)); }

inline Json vsprintf(const char *fmt, va_list ap) {
  return Json(json_vsprintf(fmt, ap)); }
template <typename T>
inline T vsprintf(const char *fmt, va_list ap) {
  return T(json_vsprintf(fmt, ap)); }

// loads
inline Json loads(const char *input, size_t flags, error_t *error) {
  return Json(json_loads(input, flags, error)); }
template <typename T>
inline T loads(const char *input, size_t flags, error_t *error) {
  return T(json_loads(input, flags, error)); }

inline Json loadb(const char *buffer, size_t buflen, size_t flags, error_t *error) {
  return Json(json_loadb(buffer, buflen, flags, error)); }
template <typename T>
inline T loadb(const char *buffer, size_t buflen, size_t flags, error_t *error) {
  return T(json_loadb(buffer, buflen, flags, error)); }

inline Json loadf(FILE *input, size_t flags, error_t *error) {
  return Json(json_loadf(input, flags, error)); }
template <typename T>
inline T loadf(FILE *input, size_t flags, error_t *error) {
  return T(json_loadf(input, flags, error)); }

inline Json loadfd(int input, size_t flags, error_t *error) {
  return Json(json_loadfd(input, flags, error)); }
template <typename T>
inline T loadfd(int input, size_t flags, error_t *error) {
  return T(json_loadfd(input, flags, error)); }

inline Json loadFile(const char *path, size_t flags, error_t *error) {
  return Json(json_load_file(path, flags, error)); }
template <typename T>
inline T loadFile(const char *path, size_t flags, error_t *error) {
  return T(json_load_file(path, flags, error)); }

inline Json loadCallback(load_callback_t callback, void *data, size_t flags, error_t *error) {
  return Json(json_load_callback(callback, data, flags, error)); }
template <typename T>
inline T loadCallback(load_callback_t callback, void *data, size_t flags, error_t *error) {
  return T(json_load_callback(callback, data, flags, error)); }

inline void setAllocFuncs(malloc_t malloc_fn, free_t free_fn) {
  return json_set_alloc_funcs(malloc_fn, free_fn); }
inline void getAllocFuncs(malloc_t *malloc_fn, free_t *free_fn) {
  return json_get_alloc_funcs(malloc_fn, free_fn); }

inline bool dumpsFree(char* sz) {
  if (!sz) return false;
  malloc_t mf = nullptr;
  free_t ff = nullptr;
  getAllocFuncs(&mf, &ff);
  if (!ff) return false;
  ff(sz);
  return true;
}

inline const char* version() { return jansson_version_str(); }
inline bool isVersion(int major, int minor, int micro) {
  return jansson_version_cmp(major, minor, micro); }

inline ErrorCode errorCode(const error_t *e) { return json_error_code(e); }

} // end namespace json

#endif // end __JSON_HPP_